
#include "DiffLibPch.h"

#include "XmlDiffResultFormatter.h"
#include "StreamDiffResultStats.h"

//
// element names
//
PCWSTR XmlDiffResultFormatter::m_rootElementName = L"FileDiffResult";
PCWSTR XmlDiffResultFormatter::m_summaryElementName = L"Summary";
PCWSTR XmlDiffResultFormatter::m_diffLinesElementName = L"DiffLines";
PCWSTR XmlDiffResultFormatter::m_diffLineElementName = L"DiffLine";
PCWSTR XmlDiffResultFormatter::m_contentElementName = L"Content";

//
// attribute names
//
PCWSTR XmlDiffResultFormatter::m_differentAttrName = L"Different";
PCWSTR XmlDiffResultFormatter::m_percentageDiffAttrName = L"PercentageDiff";
PCWSTR XmlDiffResultFormatter::m_linesTotalAttrName = L"LinesTotal";
PCWSTR XmlDiffResultFormatter::m_linesSameAttrName = L"LinesSame";
PCWSTR XmlDiffResultFormatter::m_linesAddedAttrName = L"LinesAdded";
PCWSTR XmlDiffResultFormatter::m_linesRemovedAttrName = L"LinesRemoved";
PCWSTR XmlDiffResultFormatter::m_typeAttrName = L"Type";
PCWSTR XmlDiffResultFormatter::m_lineNumberAttrName = L"LineNumber";
PCWSTR XmlDiffResultFormatter::m_sourceLineNumberAttrName = L"SourceLineNumber";
PCWSTR XmlDiffResultFormatter::m_destinationLineNumberAttrName = L"DestinationLineNumber";

//
// value names
//
PCWSTR XmlDiffResultFormatter::m_diffTypeCopy = L"Same";
PCWSTR XmlDiffResultFormatter::m_diffTypeDelete = L"Removed";
PCWSTR XmlDiffResultFormatter::m_diffTypeInsert = L"Added";

HRESULT
XmlDiffResultFormatter::Format(
	__in StreamDiffResultLineCollectionSharedPtr& diffResultLineCollection,
	__in const FileNameW& outputFileName
	)
{
	HRESULT result = E_INVALIDARG;

	if (valid(diffResultLineCollection))
	{
		result = Write((*get(diffResultLineCollection)), outputFileName);
	}

	return result;
}

HRESULT
XmlDiffResultFormatter::Write(
    __in StreamDiffResultLineCollection& diffResultLineCollection,
    __in const FileNameW& fileName
    )
{
    HRESULT result = E_INVALIDARG;
    
    if (!fileName.empty())
    {
        try
        {   
            MSXML2::IXMLDOMDocument2Ptr domDocument;
            
            result = domDocument.CreateInstance(__uuidof(MSXML2::DOMDocument60));

            if (SUCCEEDED(result))
            {
                TRACE_ASSERT(domDocument);
                
                MSXML2::IXMLDOMProcessingInstructionPtr processingInstruction;
                
                processingInstruction = domDocument->createProcessingInstruction(
                    "xml",
                    "version=\"1.0\" encoding=\"UTF-8\""
                    );
                    
                domDocument->appendChild(processingInstruction);
                
                result = Write(domDocument, diffResultLineCollection);

                if (SUCCEEDED(result))
                {
					result = PathW::Create(PathW::GetDirectoryName(fileName));
				}

				if (SUCCEEDED(result))
				{
                    MSXML2::IXMLDOMDocument2Ptr formattedDocument;

                    result = formattedDocument.CreateInstance(__uuidof(MSXML2::DOMDocument60));

                    if (SUCCEEDED(result))
                    {
                        TRACE_ASSERT(formattedDocument);

                        result = FormatXmlDocument(domDocument, formattedDocument);

                        if (SUCCEEDED(result))
                        {
                            result = formattedDocument->save(fileName.c_str());
                        }
                    }
                }                            
            }                                
        }
        catch(_com_error &e)
        {
            result = e.Error();
        }
    }
    
    return result;
}

HRESULT
XmlDiffResultFormatter::WriteAttributes(
    __inout MSXML2::IXMLDOMElementPtr& elementNode,
    __in const AttributeNameValue* nameValueArray,
    __in size_t arraySize
    )
{
	HRESULT result = E_FAIL;
    
    if (elementNode && nameValueArray && arraySize)
    {
        result = S_OK;
        
        for (size_t index = 0; (SUCCEEDED(result) && (index < arraySize)); index++)
        {
            const AttributeNameValue* currentElement = nameValueArray + index;
            
            if (!currentElement->m_value.empty())
            {            
                result = elementNode->setAttribute(
                    _bstr_t(currentElement->m_name),
                    currentElement->m_value.c_str()
                    );
            }
            else
            {
                if (!currentElement->m_optional)
                {
                    result = E_FAIL;
                    
                    break;
                }            
            }                 
        }       
    }
    
    return result;
}

HRESULT
XmlDiffResultFormatter::Write(
    __inout MSXML2::IXMLDOMDocument2Ptr& domDocument,
	__in StreamDiffResultLineCollection& diffResultLineCollection
    )
{
    TRACE_ASSERT(domDocument != NULL);
        
    MSXML2::IXMLDOMElementPtr rootNode = 
		domDocument->createElement(m_rootElementName);
    
    HRESULT result = E_FAIL;
    
    if (rootNode != NULL)
    {        
        domDocument->appendChild(rootNode);

		StreamDiffResultStats stats((*(get(diffResultLineCollection.GetDiffResults()))));

		result = Write(domDocument, rootNode, stats);

		if (SUCCEEDED(result))
		{
			result = Write(domDocument, rootNode, diffResultLineCollection);
		}
    }

    return result;
}

HRESULT
XmlDiffResultFormatter::Write(
    __inout MSXML2::IXMLDOMDocument2Ptr& domDocument,
	__inout MSXML2::IXMLDOMElementPtr parentNode,
	__in StreamDiffResultStats& summary
	)
{
    TRACE_ASSERT(domDocument != NULL);
	TRACE_ASSERT(parentNode != NULL);
 
    HRESULT result = E_FAIL;
            
    MSXML2::IXMLDOMElementPtr summaryNode = 
			domDocument->createElement(m_summaryElementName);
    
    if (summaryNode != NULL)
    {
        parentNode->appendChild(summaryNode);
        
        std::wstring different = Converter::ToStringFromBool(summary.GetDiffPercentage() > 0);
        std::wstring percentageDiff = Converter::ToString(summary.GetDiffPercentage());
		std::wstring linesTotal = Converter::ToString(summary.GetTotalCount());
		std::wstring linesSame = Converter::ToString(summary.GetCopyCount());
		std::wstring linesAdded = Converter::ToString(summary.GetInsertCount());
		std::wstring linesRemoved = Converter::ToString(summary.GetDeleteCount());
        
        AttributeNameValue attributes[] = {
            { m_differentAttrName, different, false },
            { m_percentageDiffAttrName, percentageDiff, false },
            { m_linesTotalAttrName, linesTotal, false },
            { m_linesSameAttrName, linesSame, false },
            { m_linesAddedAttrName, linesAdded, false },
            { m_linesRemovedAttrName, linesRemoved, false },
            };
            
        result = WriteAttributes(
            summaryNode,
            attributes,
            ARRAYSIZE(attributes)
            );
	}

	return result;
}

HRESULT
XmlDiffResultFormatter::Write(
    __inout MSXML2::IXMLDOMDocument2Ptr& domDocument,
	__inout MSXML2::IXMLDOMElementPtr parentNode,
	__in StreamDiffResultLineCollection& diffResultLineCollection
	)
{
    TRACE_ASSERT(domDocument != NULL);
	TRACE_ASSERT(parentNode != NULL);
 
    HRESULT result = E_FAIL;
            
    MSXML2::IXMLDOMElementPtr diffLinesNode = 
			domDocument->createElement(m_diffLinesElementName);
    
    if (diffLinesNode != NULL)
    {
        parentNode->appendChild(diffLinesNode);

		result = S_OK;

		std::wstring lineContent;

		for (
			SizeType index = 0;
			(SUCCEEDED(result) && (index < diffResultLineCollection.GetSize()));
			index++
			)
		{
			lineContent.clear();

			SizeType lineNumber = (index + 1);
			SizeType sourceLineNumber = 0;
			SizeType destinationLineNumber = 0;
			StreamDiffResult::Type lineType = StreamDiffResult::Copy;

			result = diffResultLineCollection.GetLine(
				index,
				lineContent,
				lineType,
				sourceLineNumber,
				destinationLineNumber
				);
			
			if (FAILED(result))
			{
				break;
			}
			
			result = Write(
				domDocument,
				diffLinesNode,
				lineNumber,
				lineContent,
				lineType,
				sourceLineNumber,
				destinationLineNumber
				);
		}
	}

	return result;
}

HRESULT
XmlDiffResultFormatter::Write(
    __inout MSXML2::IXMLDOMDocument2Ptr& domDocument,
    __inout MSXML2::IXMLDOMElementPtr& parentNode,
    __in SizeType lineNumber,
    __in std::wstring& lineStr,
    __in StreamDiffResult::Type& lineType,
    __in SizeType& sourceLineNumber,
    __in SizeType& destinationLineNumber
    )
{
    TRACE_ASSERT(domDocument != NULL);
	TRACE_ASSERT(parentNode != NULL);
 
    HRESULT result = E_FAIL;
            
    MSXML2::IXMLDOMElementPtr diffLineNode = 
			domDocument->createElement(m_diffLineElementName);

    MSXML2::IXMLDOMElementPtr diffContentNode = 
			domDocument->createElement(m_contentElementName);

    if ((diffLineNode != NULL) && (diffContentNode != NULL))
    {
        parentNode->appendChild(diffLineNode);
		diffLineNode->appendChild(diffContentNode);

        std::wstring type = ToString(lineType);
		std::wstring lineNumberStr = Converter::ToString(lineNumber);
		std::wstring sourceLineNumberStr = Converter::ToString(sourceLineNumber);
		std::wstring destinationLineNumberStr = Converter::ToString(destinationLineNumber);
        
        AttributeNameValue attributes[] = {
            { m_typeAttrName, type, false },
            { m_lineNumberAttrName, lineNumberStr, false },
            { m_sourceLineNumberAttrName, sourceLineNumberStr, false },
            { m_destinationLineNumberAttrName, destinationLineNumberStr, false },
            };
            
        result = WriteAttributes(
            diffLineNode,
            attributes,
            ARRAYSIZE(attributes)
            );

		if (SUCCEEDED(result))
		{
			diffContentNode->put_text(_bstr_t(lineStr.c_str()));
		}
	}

	return result;
}

HRESULT
XmlDiffResultFormatter::FormatXmlDocument(
    __in const MSXML2::IXMLDOMDocument2Ptr& inputDocument,
    __inout MSXML2::IXMLDOMDocument2Ptr& formattedDocument
    )
{
    HRESULT result = E_FAIL;

    try
    {
        MSXML2::IXMLDOMDocument2Ptr xslStyleSheet;

        result = xslStyleSheet.CreateInstance(__uuidof(MSXML2::DOMDocument60));

        if (SUCCEEDED(result))
        {
            TRACE_ASSERT(xslStyleSheet);
            
            //
            // Tabs within each line are needed
            // Without those tabs, xml document will not be having correct indentation
            //
            static PCWSTR sheetContents = 
                L"<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0'>"
                L"  <xsl:output method='xml' version='1.0' indent='yes' encoding='UTF-8'/>"
                L"  <xsl:template match='@* | node()\'>"
                L"      <xsl:copy>"
                L"          <xsl:apply-templates select='@* | node()\'/>"
                L"      </xsl:copy>"
                L"  </xsl:template>"
                L"</xsl:stylesheet>";

            VARIANT_BOOL value = xslStyleSheet->loadXML(
                _bstr_t(sheetContents)
                );

            CComQIPtr<IDispatch> dispatchPtr(formattedDocument);

            if (dispatchPtr)
            {
                CComVariant variant(dispatchPtr);

                result = inputDocument->transformNodeToObject(
                    xslStyleSheet,
                    variant
                    );
            }
        }
    }
    catch(_com_error &e)
    {
        result = e.Error();
    }

    return result;
}
